Completed
Push — master ( ad9644...aae0d8 )
by Jeff
02:45
created

Content.isPreloaded   B

Complexity

Conditions 6
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 6
c 2
b 0
f 0
nc 4
nop 0
dl 0
loc 16
rs 8.8571
1
/** global: updateScreenUrl */
2
3
/**
4
 * Screen class constructor
5
 * @param {string} updateScreenUrl global screen update checks url
6
 */
7
function Screen(updateScreenUrl) {
8
  this.fields = [];
9
  this.url = updateScreenUrl;
10
  this.lastChanges = null;
11
  this.endAt = null;
12
  this.nextUrl = null;
13
  this.stopping = false;
14
  this.cache = {};
15
}
16
17
/**
18
 * Ajax GET on updateScreenUrl to check lastChanges timestamp and reload if necessary
19
 */
20
Screen.prototype.checkUpdates = function() {
21
  var s = this;
22
  $.get(this.url, function(j) {
23
    if (j.success) {
24
      if (s.lastChanges == null) {
25
        s.lastChanges = j.data.lastChanges;
26
      } else if (s.lastChanges != j.data.lastChanges) {
27
        s.reload();
28
        s.nextUrl = null;
29
        return;
30
      }
31
32
      if (j.data.duration > 0) {
33
        // Setup next screen
34
        s.reload(j.data.duration * 1000);
35
        s.nextUrl = j.data.nextScreenUrl;
36
      }
37
    }
38
  });
39
}
40
41
/**
42
 * Start Screen reload procedure, checking for every field timeout
43
 */
44
Screen.prototype.reload = function(minDuration) {
45
  var endAt = Date.now() + (minDuration ? minDuration : 0);
46
  if (this.stopping && this.endAt < endAt) {
47
    return;
48
  }
49
50
  this.endAt = minDuration ? Date.now() + minDuration : 0;
51
  this.stopping = true;
52
  for (var i in this.fields) {
53
    if (!this.fields.hasOwnProperty(i)) {
54
      continue;
55
    }
56
    var f = this.fields[i];
57
    if (f.timeout && f.endAt > this.endAt) {
58
      this.endAt = f.endAt;
59
    }
60
  }
61
62
  if (this.endAt === 0) {
63
    this.doReload();
64
  }
65
}
66
67
/**
68
 * Actual Screen reload action
69
 */
70
Screen.prototype.doReload = function() {
71
  if (this.nextUrl) {
72
    window.location = this.nextUrl;
73
  } else {
74
    window.location.reload();
75
  }
76
}
77
78
/**
79
 * Check every field for content
80
 * @param  {Content} data 
81
 * @return {boolean} content is displayed
82
 */
83
Screen.prototype.displaysData = function(data) {
84
  return this.fields.filter(function(field) {
85
    return field.current && field.current.data == data;
86
  }).length > 0;
87
}
88
89
/**
90
 * Content class constructor
91
 * @param {array} c content attributes
92
 */
93
function Content(c) {
94
  this.id = c.id;
95
  this.data = c.data;
96
  this.duration = c.duration * 1000;
97
  this.type = c.type;
98
  this.displayCount = 0;
99
  this.src = null;
100
101
  if (this.shouldPreload()) {
102
    this.preload();
103
  }
104
}
105
106
/**
107
 * Check if content should be ajax preloaded
108
 * @return {boolean}
109
 */
110
Content.prototype.shouldPreload = function() {
111
  return this.canPreload() && !this.isPreloading() && !this.isPreloaded();
112
}
113
114
/**
115
 * Check if content has pre-loadable material
116
 * @return {boolean} 
117
 */
118
Content.prototype.canPreload = function() {
119
  return this.getResource() && this.type.search(/Video|Image|Agenda/) != -1;
120
}
121
122
/**
123
 * Extract url from contant data
124
 * @return {string} resource url
125
 */
126
Content.prototype.getResource = function() {
127
  if (this.src) {
128
    return this.src;
129
  }
130
  var srcMatch = this.data.match(/src="([^"]+)"/);
131
  if (!srcMatch) {
132
    return false;
133
  }
134
  var src = srcMatch[1];
135
  if (src.indexOf('/') === 0) {
136
    src = window.location.origin + src;
137
  }
138
  if (src.indexOf('http') !== 0) {
139
    return false;
140
  }
141
142
  this.src = src;
143
  return src;
144
}
145
146
/**
147
 * Check cache for preload status of content
148
 * @return {Boolean} 
149
 */
150
Content.prototype.isPreloaded = function() {
151
  if (!this.canPreload()) {
152
    return true;
153
  }
154
155
  var cache = screen.cache[this.getResource()]
156
  switch (cache) {
157
    case undefined: // unset
158
    case false: // preloading
159
      return false;
160
    case true: // preloaded without expire
161
      return true;
162
    default: // check expire
163
      return (new Date()).valueOf() < cache;
164
  }
165
}
166
167
/**
168
 * Set content cache status
169
 * @param {string} expires header
170
 */
171
Content.prototype.setPreloaded = function(expires) {
172
  if (expires === null) {
173
    // Missing Expires header, do not cache
174
    screen.cache[this.getResource()] = true;
175
  }
176
177
  if (expires) {
178
    var exp = new Date(expires).valueOf();
179
    var diff = exp - (new Date()).valueOf();
180
    // Do not cache short Expires
181
    screen.cache[this.getResource()] = diff < 10000 ? true : exp + 5000;
182
  } else {
183
    // Discard now expired content
184
    delete screen.cache[this.getResource()];
185
  }
186
}
187
188
/**
189
 * Check cache for in progress preloading
190
 * @return {Boolean}
191
 */
192
Content.prototype.isPreloading = function() {
193
  return screen.cache[this.getResource()] === false;
194
}
195
196
/**
197
 * Set content preloading status
198
 * @param {boolean} state is preloading
199
 */
200
Content.prototype.setPreloading = function(state) {
201
  if (state) {
202
    screen.cache[this.getResource] = false;
203
  } else if (this.isPreloading()) {
204
    delete screen.cache[this.getResource()];
205
  }
206
}
207
208
/**
209
 * Ajax call to preload content
210
 * @return {[type]} [description]
211
 */
212
Content.prototype.preload = function() {
213
  var src = this.getResource();
214
  if (!src) {
215
    this.setPreloaded(true);
216
    return;
217
  }
218
  this.setPreloading(true);
219
220
  var c = this;
221
  $.ajax({
222
    method: 'GET',
223
    url: src,
224
  }).done(function(data, textStatus, jqXHR) {
225
    c.setPreloaded(jqXHR.getResponseHeader('Expires'));
226
  }).fail(function() {
227
    c.setPreloaded(false); // Discard until next Content init
228
  });
229
}
230
231
/**
232
 * Field class constructor
233
 * @param {jQuery.Object} $f field object
234
 */
235
function Field($f) {
236
  this.$field = $f;
237
  this.id = $f.attr('data-id');
238
  this.url = $f.attr('data-url');
239
  this.types = $f.attr('data-types').split(' ');
240
  this.canUpdate = this.url != null;
241
  this.contents = [];
242
  this.previous = null;
243
  this.current = null;
244
  this.next = null;
245
  this.timeout = null;
246
  this.endAt = null;
247
}
248
249
/**
250
 * Retrieves contents from backend for this field
251
 */
252
Field.prototype.getContents = function() {
253
  if (!this.canUpdate) {
254
    return;
255
  }
256
257
  var f = this;
258
  $.get(this.url, function(j) {
259
    if (j.success) {
260
      f.contents = j.next.map(function(c) {
261
        return new Content(c);
262
      });
263
      if (!f.timeout && f.contents.length) {
264
        f.pickNext();
265
      }
266
    } else {
267
      f.setError(j.message || 'Error');
268
    }
269
  });
270
}
271
272
/**
273
 * Display error in field text
274
 */
275
Field.prototype.setError = function(err) {
276
  this.$field.text(err);
277
}
278
279
/**
280
 * Sort by displayCount and randomize order when equal displayCount
281
 */
282
Field.prototype.randomizeSortContents = function() {
283
  this.contents = this.contents.sort(function(a, b) {
284
    if (a.displayCount === b.displayCount) {
285
      return Math.random() - 0.5;
286
    }
287
    return a.displayCount - b.displayCount;
288
  });
289
}
290
291
/**
292
 * Loop through field contents to pick next displayable content
293
 */
294
Field.prototype.pickNext = function() {
295
  if (screen.stopping && screen.endAt < Date.now()) { // Stoping screen
296
    screen.doReload();
297
    return;
298
  }
299
300
  this.previous = this.current;
301
  this.current = null;
302
  var pData = this.previous && this.previous.data;
303
  // Avoid repeat & other field same content
304
  //this.randomizeSortContents();
305
  for (var i = 0; i < this.contents.length; i++) {
306
    var c = this.contents[i];
307
    // Skip too long or not preloaded content 
308
    if ((screen.endAt != null && c.duration + Date.now() > screen.endAt) || !c.isPreloaded()) {
309
      continue;
310
    }
311
312
    if (c.data == pData) {
313
      // Will repeat, avoid if enough content
314
      if (this.contents.length < 2) {
315
        this.next = c;
316
        break;
317
      }
318
      continue;
319
    }
320
321
    if (screen.displaysData(c.data)) {
322
      // Same content already displayed on other field, avoid if enough content
323
      if (this.contents.length < 3) {
324
        this.next = c;
325
        break;
326
      }
327
      continue;
328
    }
329
330
    this.next = c;
331
  }
332
333
  this.display();
334
}
335
336
/**
337
 * Display next content in field html
338
 */
339
Field.prototype.display = function() {
340
  var f = this;
341
  if (this.next && this.next.duration > 0) {
342
    this.current = this.next
343
    this.current.displayCount++;
344
    this.next = null;
345
    this.$field.html(this.current.data);
346
    this.$field.show();
347
    if (this.$field.text() != '') {
348
      this.$field.textfill({
349
        maxFontPixels: 0,
350
      });
351
    }
352
    if (this.timeout) {
353
      clearTimeout(this.timeout);
354
    }
355
    this.timeout = setTimeout(function() {
356
      f.pickNext();
357
    }, this.current.duration);
358
    this.endAt = this.current.duration + Date.now()
359
  } else {
360
    this.timeout = setTimeout(function() {
361
      f.pickNext();
362
    }, 2000);
363
  }
364
}
365
366
/**
367
 * jQuery.load event
368
 * Initialize Screen and Fields
369
 * Setup updates interval timeouts
370
 */
371
var screen = null;
372
373
function onLoad() {
374
  screen = new Screen(updateScreenUrl);
375
  // Init
376
  $('.field').each(function() {
377
    var f = new Field($(this));
378
    f.getContents();
379
    screen.fields.push(f);
380
  });
381
382
  // Setup content updates loop
383
  setInterval(function() {
384
    for (var f in screen.fields) {
385
      if (screen.fields.hasOwnProperty(f)) {
386
        screen.fields[f].getContents();
387
      }
388
    }
389
    screen.checkUpdates();
390
  }, 60000);
391
  screen.checkUpdates();
392
}
393
394
// Run
395
$(onLoad);
396